using System;
using System.Diagnostics.Contracts;
using System.Reactive.Linq;
using Microsoft.Owin;

namespace LinqToOwin
{
  public sealed class LinqToOwinHost
  {
    public PathString BasePath
    {
      get
      {
        return basePath;
      }
    }

    public PathString RelativePath
    {
      get
      {
        return relativePath;
      }
    }

    public PathString FullPath
    {
      get
      {
        return fullPath;
      }
    }

    public LinqToOwinHost this[string path]
    {
      get
      {
        Contract.Ensures(Contract.Result<LinqToOwinHost>() != null);

        return new LinqToOwinHost(requests, relativePath, new PathString(path ?? string.Empty));
      }
    }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1043:UseIntegralOrStringArgumentForIndexers", Justification = "Lowered alternative.")]
    public LinqToOwinHost this[PathString path]
    {
      get
      {
        Contract.Ensures(Contract.Result<LinqToOwinHost>() != null);

        return new LinqToOwinHost(requests, relativePath, path);
      }
    }

    public IObservable<LinqToOwinContext> AllRequests
    {
      get
      {
        Contract.Ensures(Contract.Result<IObservable<LinqToOwinContext>>() != null);

        return relativePath.HasValue ? requests.Where(IsInPath) : requests;
      }
    }

    public IObservable<LinqToOwinContext> GetRequests
    {
      get
      {
        Contract.Ensures(Contract.Result<IObservable<LinqToOwinContext>>() != null);

        return from context in requests
               where (context.Request.Method ?? string.Empty).ToUpperInvariant() == "GET"
                  && IsInPath(context)
               select context;
      }
    }

    public IObservable<LinqToOwinContext> PutRequests
    {
      get
      {
        Contract.Ensures(Contract.Result<IObservable<LinqToOwinContext>>() != null);

        return from context in requests
               where (context.Request.Method ?? string.Empty).ToUpperInvariant() == "PUT"
                  && IsInPath(context)
               select context;
      }
    }

    public IObservable<LinqToOwinContext> PostRequests
    {
      get
      {
        Contract.Ensures(Contract.Result<IObservable<LinqToOwinContext>>() != null);

        return from context in requests
               where (context.Request.Method ?? string.Empty).ToUpperInvariant() == "POST"
                  && IsInPath(context)
               select context;
      }
    }

    public IObservable<LinqToOwinContext> DeleteRequests
    {
      get
      {
        Contract.Ensures(Contract.Result<IObservable<LinqToOwinContext>>() != null);

        return from context in requests
               where (context.Request.Method ?? string.Empty).ToUpperInvariant() == "DELETE"
                  && IsInPath(context)
               select context;
      }
    }

    private readonly IObservable<LinqToOwinContext> requests;
    private readonly PathString basePath, relativePath, fullPath;

    internal LinqToOwinHost(IObservable<LinqToOwinContext> requests)
    {
      Contract.Requires(requests != null);

      this.requests = requests;
    }

    private LinqToOwinHost(IObservable<LinqToOwinContext> requests, PathString basePath, PathString path)
    {
      Contract.Requires(requests != null);

      this.basePath = basePath;
      this.relativePath = path;
      this.fullPath = basePath.Add(relativePath);
      this.requests = requests.Do(context => context.ResponsePathBase = basePath);
    }

    [ContractInvariantMethod]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
    private void ObjectInvariant()
    {
      Contract.Invariant(requests != null);
    }

    private bool IsInPath(LinqToOwinContext context)
    {
      Contract.Requires(context != null);

      return context.Request.Path.StartsWithSegments(fullPath);
    }
  }
}